/* 
 * File:   Partida.cpp
 * Author: fibrizo
 * 
 * Created on 24 de mayo de 2014, 17:45
 */

#include "server_Partida.h"
#include "common_ParserXML.h"
#include "server_Nivel.h"
#include "server_Configuracion.h"
#include "server_Evento.h"
#include "server_ColaDeEventos.h"
#include "server_Bomberman.h"
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <queue>
#include <list>
#include <chrono>
#include <thread>
#include <unordered_map>

using std::vector;
using std::string;
using std::stringstream;
using std::stoul;
using std::list;
using std::stoi;
using std::stof;
using std::cout;
using std::endl;
using std::unordered_map;

typedef struct vector<Nivel> vectorN;
typedef struct map<string, Jugador*> hash;

/********************************************************************/
/*                           CONSTRUCTORES                          */

/********************************************************************/

Partida::Partida(Jugador* host) {
    this->cantidadJugadoresActual = 1;
    this->jugadores[host->getNombre()] = host;
    host->crearPersonaje();
    this->lista = false;
    this->cancelada = false;
    this->campaniaCargada = false;
    this->Jugando = false;
    this->colaDeEventos = new ColaDeEventos();
    this->terminada = false;
    this->iniciar = false;
    this->codigos = string("abcdefghijklmnopqrstuvwxyz");
}

/********************************************************************/

/********************************************************************/

Partida::Partida(Jugador* host, string rutaArchivoCampania) {
    //Partida::Partida(Jugador* host, Campania* campania) {
    this->cantidadJugadoresActual = 1;
    this->jugadores[host->getNombre()] = host;
    host->crearPersonaje();
    this->lista = false;
    this->cancelada = false;
    this->campaniaCargada = false;
    this->Jugando = false;
    this->cargarCampania(rutaArchivoCampania);
    this->terminada = false;
    this->colaDeEventos = new ColaDeEventos();
    this->iniciar = false;
    this->codigos = string("abcdefghijklmnopqrstuvwxyz");
}

/********************************************************************/

/********************************************************************/

Partida::Partida(const Partida& orig) {
    this->cantidadJugadoresActual = 1;
    this->jugadores = orig.jugadores;
    this->lista = orig.lista;
    this->campaniaCargada = orig.campaniaCargada;
    this->Jugando = orig.Jugando;
    this->terminada = orig.terminada;
    this->cancelada = orig.cancelada;
    this->colaDeEventos = new ColaDeEventos();
    this->iniciar = orig.iniciar;
    this->codigos = string("abcdefghijklmnopqrstuvwxyz");
}

/********************************************************************/
/*                          METODOS PRIVADOS                        */
/********************************************************************/

// Recibe por parametro un vector con los datos de N niveles.
// Esta funcion extrae de dicho vector los datos referentes
// a zombies, crea N niveles y les agrega a los zombies que
// correspondan a cada uno.
// Devuelve una vector de datos del tipo Nivel.

vectorN Partida::crearNiveles(vector<string> datos) {
    vectorN niveles;
    unsigned int i = 10;    
    while (i < datos.size()) {
        Nivel nivel = Nivel(stoi(datos[i], NULL, 10), datos[i + 1]);
        niveles.push_back(nivel);
        i += 8;
    }
    return niveles;
}


/********************************************************************/
/*                          METODOS PUBLICOS                        */

/********************************************************************/

bool Partida::cargarCampania(string rutaArchivoCampania) {
    ParserXML parser;
    vector<string> datos = parser.cargarDatos(rutaArchivoCampania);
    this->tileMapInicial = datos[11];
    vector<Nivel> niveles = crearNiveles(datos);    
    this->cantidadJugadoresRequerida = stoul(datos[3]);    
    size_t dif;  
    if (datos[2] == "Facil") dif = 1;
    else if (datos[2] == "Media") dif = 2;
    else if (datos[2] == "Dificil") dif = 3;
    else throw "Dificultad no existente";
    this->campania = Campania(datos[0], dif, niveles, datos[1], stoul(datos[3]));
    this->campaniaCargada = true;
    return true;
}

/********************************************************************/

/********************************************************************/

bool Partida::cargarDatosDeConfiguracion(string rutaArchivoConfiguracion) {
    ParserXML parser;
    vector<string> datos;
    try {
        datos = parser.cargarDatos(rutaArchivoConfiguracion);
    } catch (const char* e) {
        cout << e << endl;
    }
    // Bombas
    this->alcanceBombas = stoul(datos[0]);
    this->tiempoDetonacion = stof(datos[1]);
    // Otros tiempos
    this->tiempRefresco = stof(datos[2]);
    this->tiempoRegerenacionBomba = stof(datos[3]);
    // Velocidades de desplazamiento
    this->tiempoZombieVerde = stof(datos[4]);
    this->tiempoBomberman = stof(datos[5]);
    this->tiempoDisparo = stof(datos[6]);
    // Mapa
    this->tamanioMapa = stoul(datos[7]);
    return true;
}

/********************************************************************/

/********************************************************************/

bool Partida::estaListaParaComenzar() {
    if (!this->campaniaCargada) return false;
    if (this->cantidadJugadoresActual < this->cantidadJugadoresRequerida)
        return false;
    if (!this->iniciar) return false;
    return true;
}

/********************************************************************/

/********************************************************************/

void Partida::agregarJugador(Jugador* nuevoJugador) {
    this->jugadores[nuevoJugador->getNombre()] = nuevoJugador;
    nuevoJugador->crearPersonaje();
    this->cantidadJugadoresActual += 1;
}

/********************************************************************/

/********************************************************************/

void Partida::eliminarJugador(Jugador* jugador) {
    this->jugadores.erase(jugador->getNombre());
    this->cantidadJugadoresActual -= 1;
}

/********************************************************************/

/********************************************************************/

bool Partida::jugadoresListos() {
    for (hash::iterator it = this->jugadores.begin(); it != this->jugadores.end(); it++) {
        Jugador* actual = it->second;
        actual->setEstado(true);
        if (!actual->estaListo()) return false;
    }
    return true;
}

/********************************************************************/

/********************************************************************/

void Partida::actualizarEstado() {
    while (!colaDeEventos->estaVacia()) {
        Evento* nuevoEvento = colaDeEventos->mostrarPrimero();
        colaDeEventos->desencolar();
    
        Jugador* jugadorDisparoEvento = nuevoEvento->obtenerJugador();
        Bomberman* personaje = jugadorDisparoEvento->obtenerPersonaje();
        if (nuevoEvento->esDireccion())
            personaje->setDireccion(nuevoEvento->getNombre());
        else
            personaje->crearBomba();
    }
    this->nivelActual.actualizar();
}

bool Partida::nivelActualTerminado() {
    return (this->nivelActual).termino();
}

// Si la cantidad de jugadores es la requerida, comienza la partida
// en caso contrario se sale de la funcion.
// Devuelve True si la cantidad de jugadores era la requerida para
// comenzar la partida y esta comenzo. Devuelve False en caso 
// contrario.

bool Partida::correr() {
    vector<Bomberman*> personajes;
    for (hash::iterator it = jugadores.begin(); it != jugadores.end(); it++) {
        Jugador* jugador = (it->second);        
        personajes.push_back(jugador->obtenerPersonaje());
    }

    this->Jugando = true;
    while (Jugando) {
        if (!this->estaListaParaComenzar()) {
            return false;
        }
        terminada = false;        
        this->nivelActual = this->campania.getNivelActual();
        this->nivelActual.inicializar(nivelActual.getTileMap(), personajes);
        while (!this->nivelActualTerminado()) {
            this->actualizarEstado();
            std::this_thread::sleep_for(std::chrono::milliseconds(400));
        }

        // Reseteo las vidas de todos los personajes para el siguiente nivel
        for (unsigned int i = 0; i < personajes.size(); i++)
            personajes[i]->resetVidas();
        Jugando = this->campania.cambiarDeNivel(this->nivelActual.ganado());
    }
    this->Jugando = false;
    this->terminada = true;
    return true;
}

/********************************************************************/

/********************************************************************/

Campania Partida::obtenerCampania() {
    return this->campania;
}

/********************************************************************/
/*                              DESTRUCTOR                          */

/********************************************************************/

Partida::~Partida() {
}

string Partida::getTileMap() {
    if (!terminada)
        return nivelActual.getTileMap();
    else
        return string("S");
}

ColaDeEventos* Partida::getColaDeEventos() {
    return this->colaDeEventos;
}

string Partida::getTileMapInicial() {
    return this->tileMapInicial;
}

void Partida::setNombre(string nombre) {
    this->nombre = nombre;
}

string Partida::getNombre() {
    return this->nombre;
}

unsigned int Partida::getCantJugadoresActual() {
    return this->cantidadJugadoresActual;
}

unsigned int Partida::getCantJugadoresRequerida() {
    return this->cantidadJugadoresRequerida;
}

void Partida::setFlagIniciar() {
    this->iniciar = true;
}

void Partida::cancelar() {
    this->cancelada = true;
}

bool Partida::fueCancelada() {
    return this->cancelada;
}

unordered_map<string,string> Partida::elegirSerialParaJugador(){
    string codigo;
    unordered_map<string, string> dir;
    vector<string> dirs = {"abajo", "arriba", "derecha", "izquierda"};
    int i = 1;
    int indice = i;
    do {
        indice = i; //+ this->cantidadJugadoresActual;
        codigo = codigos.substr(indice,1);
        i++;
    } while (noDisponibles.count(codigo) > 0);

    for (int j = 0; j < 4; j++) {
        dir[dirs[j]] = codigos.substr(indice,1);
        noDisponibles.insert(dir[dirs[j]]);
        indice += 5;
    }
    return dir;
}

unordered_map<string, string> Partida::elegirSerialParaHost() {
    int i = 0;
    unordered_map<string, string> dir;
    vector<string> dirs = {"abajo", "arriba", "derecha", "izquierda"};
    for (int j = 0; j < 4; j++) {
        dir[dirs[j]] = codigos.substr(i,1);
        noDisponibles.insert(dir[dirs[j]]);
        i += 5;
    }
    return dir;
}

bool Partida::estaJugando(){
    return this->Jugando;
}

void Partida::setFlagEmpezar(){
    this->empezar = true;
}

bool Partida::empezarActivado(){
    return this->empezar;
}